.. _Конструктор и другие специальные функции:

Конструктор и другие специальные функции
========================================

.. index::
    constructor
    конструктор

Хотя эта глава посвящена только структурам, темы, которые здесь рассматриваются
в основном применимы и к классам. Различия будут объяснены в последующих главах.
Четыре функции-члена структур являются специальными, поскольку они определяют
фундаментальные операции этого типа:
* this() для создания
* ~this() для уничтожения
* this(ref const(S)) для построения копии
(S - это просто пример, представляющий тип структуры).
* opAssign() для присваивания.
Кроме того, есть унаследованная функция, которую не рекомендуется использовать для вновь
написанном коде:
* this(this) для постблиц.
Для структур эти фундаментальные операции выполняются автоматически. Но можно
можно определить их вручную, чтобы обеспечить различные реализации в случае необходимости.

Конструктор
-----------

В обязанности конструктора входит подготовка объекта к использованию путем присвоения
соответствующих значений его членам.
Мы уже использовали конструкторы в предыдущих главах. Когда имя
типа используется как функция, на самом деле вызывается именно конструктор. Мы можем
это видно в правой части следующей строки:

.. code-block:: d

    auto busArrival = TimeOfDay(8, 30);

Аналогичным образом строится объект класса в правой части
следующей строки:

.. code-block:: d

    auto variable = new SomeClass();


Аргументы, указанные в круглых скобках, соответствуют
параметрам конструктора. Например, значения 8 и 30, указанные выше, передаются в конструктор
TimeOfDay в качестве параметров конструктора.
В дополнение к различным синтаксисам построения объектов, с которыми мы познакомились до сих пор;
const, неизменяемые и разделяемые объекты могут быть созданы с помощью синтаксиса конструктора типов
конструктора (например, как immutable(S)(2)). (Мы познакомимся с ключевым словом shared
в одной из следующих глав (стр. 640).)
Например, хотя все три переменные ниже являются неизменяемыми, конструкция
конструкция переменной a семантически отличается от конструкций
переменных b и c:

.. code-block:: d

    /* More familiar syntax; immutable variable of a mutable
     * type: */
    immutable a = S(1);
    /* Type constructor syntax; a variable of an immutable
     * type: */
     auto b = immutable(S)(2);
    /* Same meaning as 'b' */
    immutable c = immutable(S)(3);


**Синтаксис конструктора**

В отличие от других функций, конструкторы не имеют возвращаемых значений. Имя
конструктора всегда this


.. code-block:: d

    struct SomeStruct
    {
        // ...
        this( /* constructor parameters */ )
        {
            // ... operations that prepare the object for use ...
        }
    }


Параметры конструктора содержат информацию, необходимую для создания полезного и консистентного
объекта

**Автоматический конструктор, генерируемый компилятором**

Все структуры, которые мы рассматривали до сих пор, использовали преимущества
конструктора, который был автоматически сгенерирован компилятором. Автоматический конструктор
автоматический конструктор присваивает значения параметров членам в том порядке.
в котором они указаны.
Как вы помните из главы "Структуры" (стр. 246), начальные значения для
не нужно указывать. Члены, которые не указаны
инициализируются значением .init соответствующих типов. Значения .init для
члена могут быть указаны во время определения этого члена после оператора =

.. code-block:: d

    struct Test
    {
        int member = 42;
    }

Также учитывая возможность использования значений параметров по умолчанию из главы "Переменное число параметров
параметров (стр. 258), мы можем представить, что автоматический конструктор для
следующей структуры был бы эквивалентен следующему this():

.. code-block:: d

    struct Test
    {
        char c;
        int i;
        double d;
        /* The equivalent of the compiler-generated automatic
        * constructor (Note: This is only for demonstration; the
        * following constructor would not actually be called
        * when default-constructing the object as Test().) */
        this(in char c_parameter = char.init,
            in int i_parameter = int.init,
            in double d_parameter = double.init)
        {
            c = c_parameter;
            i = i_parameter;
            d = d_parameter;
        }
    }

Для большинства структур достаточно конструктора, сгенерированного компилятором: Предоставление
соответствующие значения для каждого члена - это все, что нужно для создания объектов
конструирования.

.. index::
    this

**Доступ к членам через this.**

Чтобы не смешивать параметры с членами, к именам параметров выше
добавляли к именам параметров _parameter. Без этого возникли бы ошибки компиляции


.. code-block:: d

    struct Test
    {
        char c;
        int i;
        double d;
        this(in char c = char.init,
            in int i = int.init,
            in double d = double.init)
        {
            // An attempt to assign an 'in' parameter to itself!
            c = c; // ← ошибка компиляции
            i = i;
            d = d;
        }
    }

Причина в том, что c означает параметр, а не член, а поскольку
параметры выше определены как in, они не могут быть изменены:

.. code-block:: console

    Error: variable deneme.Test.this.c cannot modify const

Решение состоит в том, чтобы добавлять к именам членов this. Внутри функций-членов
функций this означает "этот объект", а this.c - "член c этого
объекта":

.. code-block:: d

    this(in char c = char.init,
         in int i = int.init,
         in double d = double.init)
    {
        this.c = c;
        this.i = i;
        this.d = d;
    }

Теперь c означает только параметр, а this.c - член, и код
компилируется и работает, как и ожидалось: Член c инициализируется значением
параметра c.

**Определяемые пользователем конструкторы**

Я описал поведение конструктора, генерируемого компилятором. Поскольку этот
конструктор подходит для большинства случаев, нет необходимости определять конструктор вручную
вручную.
Тем не менее, бывают случаи, когда конструирование объекта включает в себя более сложные
операции, чем присвоение значений каждому члену по порядку. В качестве примера давайте
рассмотрим Duration из предыдущих глав:

.. code-block:: d

    struct Duration
    {
        int minute;
    }

Для этой одночленной структуры достаточно сгенерированного компилятором конструктора:

.. code-block:: d

    time.decrement(Duration(12));

Поскольку этот конструктор принимает длительность в минутах, программистам иногда приходится
иногда приходится производить вычисления:

.. code-block:: d

    // 23 hours and 18 minutes earlier
    time.decrement(Duration(23 * 60 + 18));
    // 22 hours and 20 minutes later
    time.increment(Duration(22 * 60 + 20));

Чтобы устранить необходимость в этих вычислениях, мы можем разработать конструктор Duration
конструктор, который принимает два параметра и производит вычисления автоматически:

.. code-block:: d

    struct Duration
    {
        int minute;
        this(int hour, int minute)
        {
            this.minute = hour * 60 + minute;
        }
    }

Поскольку часы и минуты теперь являются отдельными параметрами, пользователи просто предоставляют
их значения, не прибегая к самостоятельному вычислению:

.. code-block:: d

    // 23 hours and 18 minutes earlier
    time.decrement(Duration(23, 18));
    // 22 hours and 20 minutes later
    time.increment(Duration(22, 20));

**Первое присвоение члену является конструктором**

При установке значений членов в конструкторе первое присвоение каждому члену
члену обрабатывается особым образом: Вместо того чтобы присваивать новое значение поверх значения .init
первое присваивание фактически конструирует этот член.
Дальнейшие присваивания этому члену рассматриваются как обычные операции присваивания
операции.
Это особое поведение необходимо для того, чтобы неизменяемые и const-члены могли
быть сконструированы со значениями, известными только во время выполнения. В противном случае они могли бы
никогда не были бы установлены в нужные значения, поскольку присваивание запрещено для неизменяемых и
const переменных.
Следующая программа демонстрирует, как операция присваивания разрешается только
один раз для неизменяемого члена:

.. code-block:: d

    struct S
    {
        int m;
        immutable int i;
        this(int m, int i)
        {
            this.m = m; // ← construction
            this.m = 42; // ← assignment (possible for mutable member)
            this.i = i; // ← construction
            this.i = i; // ← compilation ERROR
        }
    }

    void main()
    {
        auto s = S(1, 2);
    }

**Определяемый пользователем конструктор отключает генерируемый компилятором конструктор**

Конструктор, определенный программистом, делает некоторые варианты использования
конструктора, созданного компилятором, недействительными: Объекты не могут быть построены по заданным по умолчанию
значениям параметров по умолчанию. Например, попытка сконструировать Duration по единственному
является ошибкой компиляции:

.. code-block:: d

    time.decrement(Duration(12)); // ← ошибка компиляции

Ошибка компиляции связана с тем, что конструктор программиста не принимает
не принимает ни одного параметра, а сгенерированный компилятором конструктор отключен.
Одно из решений - перегрузить конструктор, предоставив другой конструктор
который принимает только один параметр:

.. code-block:: d

    struct Duration
    {
        int minute;
        this(int hour, int minute)
        {
            this.minute = hour * 60 + minute;
        }

        this(int minute)
        {
            this.minute = minute;
        }
    }

Определяемый пользователем конструктор отключает построение объектов с помощью синтаксиса { }.
также:

.. code-block:: d

    Duration duration = { 5 }; // ← ошибка компиляции

Инициализация без указания каких-либо параметров по-прежнему актуальна:

.. code-block::

    auto d = Duration(); // compiles

Причина в том, что в D значение .init каждого типа должно быть известно во время компиляции.
Вышеприведенное значение d равно начальному значению Duration:

.. code-block:: d

    assert(d == Duration.init);

**статический opCall вместо конструктора по умолчанию**

Поскольку начальное значение каждого типа должно быть известно во время компиляции,
невозможно определить конструктор по умолчанию в явном виде.
Рассмотрим следующий конструктор, который пытается вывести некоторую информацию
каждый раз, когда создается объект данного типа:

.. code-block:: d

    struct Test
    {
        this()
        { // ← ошибка компиляции
            writeln("A Test object is being constructed.");
        }
    }

Вывод компилятора

.. code-block:: console

    Error: constructor deneme.Deneme.this default constructor for structs only allowed with @disable and no body

*Примечание: В последующих главах мы увидим, что можно определить конструктор по умолчанию для
классов.*

В качестве обходного пути можно использовать статический opCall() без параметров для
для конструирования объектов без указания параметров. Обратите внимание, что это никак не
не влияет на значение .init типа.
Чтобы это работало, статический opCall() должен построить и вернуть объект такого типа
типа struct:

.. code-block:: d

    import std.stdio;

    struct Test
    {
        static Test opCall()
        {
            writeln("A Test object is being constructed.");
            Test test;
            return test;
        }
    }

    void main()
    {
        auto test = Test();
    }

Вызов Test() в main() выполняет статическую функцию opCall()

.. code-block:: console

    A Test object is being constructed.

Обратите внимание, что внутри статического opCall() нельзя вводить Test(). Такой синтаксис
снова выполнит static opCall() и вызовет бесконечную рекурсию:

.. code-block:: d

    static Test opCall() {
        writeln("A Test object is being constructed.");
        return Test(); // ← Calls 'static opCall()' again
    }

Вывод

.. code-block:: console

    A Test object is being constructed.
    A Test object is being constructed.
    A Test object is being constructed.
    ... ← repeats the same message

**Вызов других конструкторов**

Конструкторы могут вызывать другие конструкторы, чтобы избежать дублирования кода. Хотя
Duration слишком прост, чтобы продемонстрировать, насколько полезна эта возможность, следующий
однопараметрический конструктор использует преимущества двухпараметрического конструктора

.. code-block:: d

    this(int hour, int minute)
    {
        this.minute = hour * 60 + minute;
    }

    this(int minute)
    {
        this(0, minute); // calls the other constructor
    }

Конструктор, который принимает только значение минуты, вызывает другой конструктор, передавая
передавая 0 в качестве значения часа.
Внимание: В приведенных выше конструкторах Duration есть недостаток проектирования, поскольку
намерения не ясны, когда объекты конструируются по одному параметру:

.. code-block:: d

    // 10 hours or 10 minutes?
    auto travelDuration = Duration(10);

Хотя можно определить, прочитав документацию или код
struct, что параметр на самом деле означает "10 минут", это является несоответствием
поскольку первым параметром двухпараметрического конструктора являются часы.
Подобные ошибки проектирования являются причиной ошибок и должны быть исключены.

**Квалификаторы конструкторов**

Обычно один и тот же конструктор используется для мутабельных, константных, неизменяемых и
разделяемых объектов:

.. code-block:: d

    import std.stdio;

    struct S
    {
        this(int i)
        {
            writeln("Constructing an object");
        }
    }

    void main()
    {
        auto m = S(1);
        const c = S(2);
        immutable i = S(3);
        shared s = S(4);
    }

Семантически, объекты, построенные в правых частях этих
все мутабельные; только переменные имеют различные классификаторы типов. Для всех них используется один и тот же конструктор:

.. code-block:: console

    Constructing an object
    Constructing an object
    Constructing an object
    Constructing an object

В зависимости от классификатора результирующего объекта, иногда некоторые члены могут
могут быть инициализированы по-другому или не инициализироваться вообще. Например,поскольку ни один член неизменяемого объекта не может быть изменен в течение всего времени жизни
ни один из членов неизменяемого объекта не может быть изменен, оставление его изменяемых членов без инициализации может повысить производительность программы
производительность.
Квалифицированные конструкторы могут быть определены по-разному для объектов с различными
квалификаторами:

.. code-block:: d

    import std.stdio;

    struct S
    {
        this(int i)
        {
            writeln("Constructing an object");
        }

        this(int i) const
        {
            writeln("Constructing a const object");
        }

        this(int i) immutable
        {
            writeln("Constructing an immutable object");
        }

        // We will see the 'shared' keyword in a later chapter.
        this(int i) shared
        {
            writeln("Constructing a shared object");
        }
    }

    void main()
    {
        auto m = S(1);
        const c = S(2);
        immutable i = S(3);
        shared s = S(4);
    }

Однако, как указано выше, поскольку все выражения правой части являются
семантически мутабельны, эти объекты все равно конструируются с помощью конструктива мутабельных объектов
конструктива:

.. code-block:: console

    Constructing an object
    Constructing an object ← NOT the const constructor
    Constructing an object ← NOT the immutable constructor
    Constructing an object ← NOT the shared constructon

Чтобы воспользоваться преимуществами квалифицированных конструкторов, необходимо использовать синтаксис конструктора типа
синтаксис. (Термин "конструктор типов" не следует путать с термином "объектный
конструкторами; конструктор типов относится к типам, а не к объектам.) Этот синтаксис делает
другой тип, комбинируя классификатор с существующим типом. Например,
immutable(S) - это квалифицированный тип, образованный из immutable и S:

.. code-block:: d

    auto m = S(1);
    auto c = const(S)(2);
    auto i = immutable(S)(3);
    auto s = shared(S)(4);

На этот раз объекты в правых выражениях разные: mutable,
const, immutable и shared, соответственно. В результате каждый объект
создается с помощью соответствующего конструктора:

.. code-block:: console

    Constructing an object
    Constructing a const object
    Constructing an immutable object
    Constructing a shared object

Как и ожидалось, поскольку все вышеперечисленные переменные определены с помощью ключевого слова auto,
они правильно определяются как изменяемые, const, неизменяемые и разделяемые,
соответственно.

**Неизменяемость параметров конструктора**

В главе "Неизменяемость" (стр. 147) мы видели, что нелегко решить.
следует ли определять параметры ссылочных типов как const или как неизменяемые.
Хотя те же соображения применимы и к параметрам конструктора,
неизменяемые обычно лучше выбирать для параметров конструктора.
Причина в том, что обычно параметры присваиваются членам, которые будут использоваться в
позднее. Если параметр не является неизменяемым, нет никакой гарантии, что
исходная переменная не изменится к моменту использования члена.
Рассмотрим конструктор, принимающий в качестве параметра имя файла. Имя файла
будет использоваться в дальнейшем при написании оценок студентов. В соответствии с рекомендациями, приведенными в
главе "Неизменяемость" (стр. 147), чтобы быть более полезным, предположим, что параметр конструктора
параметр конструктора определен как const char[]

.. code-block:: d

    import std.stdio;

    struct Student
    {
        const char[] fileName;
        int[] grades;
        this(const char[] fileName)
        {
            this.fileName = fileName;
        }

        void save()
        {
            auto file = File(fileName.idup, "w");
            file.writeln("The grades of the student:");
            file.writeln(grades);
        }
        // ...
    }

    void main()
    {
        char[] fileName;
        fileName ~= "student_grades";
        auto student = Student(fileName);
        // ...
        /* Assume the fileName variable is modified later on
        * perhaps unintentionally (all of its characters are
        * being set to 'A' here): */
        fileName[] = 'A';
        // ...
        /* The grades would be written to the wrong file: */
        student.save();
    }

Приведенная выше программа сохраняет оценки студента в имени файла, состоящем из
символов A, а не в "student_grades". По этой причине иногда целесообразнее
определить параметры конструктора и члены ссылочных типов как
неизменяемыми. Мы знаем, что для строк это легко сделать, используя псевдонимы типа string. На
Следующий код показывает части структуры, которые необходимо изменить

.. code-block:: d

    struct Student
    {
        string fileName;
        // ...
        this(string fileName)
        {
        // ...
        }
        // ...
    }

Теперь пользователи структуры должны предоставлять неизменяемые строки, и в результате
путаница с именем файла будет предотвращена.

**Преобразование типов с помощью однопараметрических конструкторов**

Конструкторы с одним параметром можно рассматривать как своего рода преобразование типа
преобразование: Они создают объект определенного типа struct, начиная с параметра
параметра конструктора. Например, следующий конструктор создает объект
объект Student из строки:

.. code-block:: d

    struct Student
    {
        string name;
        this(string name)
        {
            this.name = name;
        }
    }

to() и наблюдаем такое поведение при преобразовании. Чтобы увидеть примеры
этого, рассмотрим следующую функцию salute(). Отправка строкового параметра
когда ожидается параметр Student, естественно, приведет к ошибке компиляции:

.. code-block:: d

    void salute(Student student)
    {
        writeln("Hello ", student.name);
    }
    // ...
    salute("Jane"); // ← ошибка компиляции

С другой стороны, все следующие строки гарантируют, что перед вызовом функции будет создан объект Student
перед вызовом функции:

.. code-block:: d

    import std.conv;
    // ...
    salute(Student("Jane"));
    salute(to!Student("Jean"));
    salute(cast(Student)"Jim");

to и cast воспользовались преимуществами однопараметрического конструктора, создав
временного объекта Student и вызова функции salute() с этим объектом.

.. index::
    destructor
    деструктор

Деструктор
----------

Деструктор включает в себя операции, которые должны быть выполнены, когда время жизни
объекта.
Сгенерированный компилятором автоматический деструктор выполняет деструкторы всех
членов по порядку. По этой причине, как и в случае с конструктором, нет необходимости
нет необходимости определять деструктор для большинства структур.
Однако иногда может потребоваться выполнить некоторые специальные операции, когда
время жизни объекта. Например, ресурс операционной системы, которым владеет объект
может быть возвращен системе; может потребоваться вызвать функцию-член другого
может потребоваться вызов; сервер, работающий где-то в сети, может
может потребоваться уведомление о том, что соединение с ним скоро будет прервано, и т. д.
Деструктор называется ~this и, как и конструкторы, не имеет возвращаемого
тип.

**Деструктор выполняется автоматически**

Деструктор выполняется, как только заканчивается время жизни объекта struct. (Это
не относится к объектам, созданным с помощью ключевого слова new.)
Как мы уже видели в главе "Время жизни и основные операции" (стр.
228), время жизни объекта заканчивается, когда он покидает область видимости, в которой он определен. На
Ниже перечислены моменты, когда время жизни структуры заканчивается:

* При выходе из области видимости объекта либо в обычном режиме, либо из-за брошенного
  исключения:

.. code-block:: d

    if (aCondition)
    {
        auto duration = Duration(7);
        // ...
    } // ← The destructor is executed for 'duration'
     // at this point

Анонимные объекты уничтожаются в конце всего выражения, в котором они
в котором они были созданы:

.. code-block:: d

    time.increment(Duration(5)); // ← The Duration(5) object
    // gets destroyed at the end
    // of the whole expression

Все члены struct объекта struct уничтожаются, когда внешний объект
уничтожаются.

**Пример деструктора**

Давайте разработаем тип для генерации простых XML-документов. Элементы XML
определяются угловыми скобками. Они содержат данные и другие элементы XML. XML
Элементы также могут иметь атрибуты; здесь мы будем их игнорировать.
Наша цель - обеспечить, чтобы элемент, открытый тегом <имя>
всегда закрывался соответствующим тегом </name>:

.. code-block:: xml

    <class1> ← opening the outer XML element
        <grade> ← opening the inner XML element
            57 ← the data
        </grade> ← closing the inner element
    </class1> ← closing the outer element

Структура, которая может выводить вышеописанный результат, может быть сконструирована из двух членов, которые
хранят тег для элемента XML и отступ, который нужно использовать при его печати:

.. code-block:: d

    struct XmlElement
    {
        string name;
        string indentation;
    }

Если обязанности по открытию и закрытию элемента XML возложены на
конструктору и деструктору соответственно, можно получить желаемый результат
управляя временем жизни объектов XmlElement. Например, конструктор
может вывести <tag>, а деструктор - </tag>.
Следующее определение конструктора выводит открывающий тег:

.. code-block:: d

    this(string name, int level)
    {
        this.name = name;
        this.indentation = indentationString(level);
        writeln(indentation, '<', name, '>');
    }

indentationString() - это следующая функция:

.. code-block:: d

    import std.array;
    // ...
    string indentationString(int level)
    {
        return replicate(" ", level * 2);
    }

Функция вызывает функцию replicate() из модуля std.array, которая создает и
возвращает новую строку, состоящую из указанного значения, повторенного указанное
количество раз.
Деструктор может быть определен аналогично конструктору, чтобы получить закрытие
тег:

.. code-block:: d

    ~this()
    {
        writeln(indentation, "</", name, '>');
    }

Вот тестовый код, демонстрирующий эффект от автоматического вызова конструктора и
вызова деструктора:

.. code-block:: d

    import std.conv;
    import std.random;
    import std.array;

    string indentationString(int level)
    {
        return replicate(" ", level * 2);
    }

    struct XmlElement
    {
        string name;
        string indentation;
        this(string name, int level)
        {
            this.name = name;
            this.indentation = indentationString(level);
            writeln(indentation, '<', name, '>');
        }

        ~this()
        {
            writeln(indentation, "</", name, '>');
        }
    }

    void main()
    {
        immutable classes = XmlElement("classes", 0);
        foreach (classId; 0 .. 2)
        {
            immutable classTag = "class" ~ to!string(classId);
            immutable classElement = XmlElement(classTag, 1);
            foreach (i; 0 .. 3)
            {
                immutable gradeElement = XmlElement("grade", 2);
                immutable randomGrade = uniform(50, 101);
                writeln(indentationString(3), randomGrade);
            }
        }
    }

Обратите внимание, что объекты XmlElement создаются в трех отдельных областях в
программе выше. Открывающие и закрывающие теги элементов XML в выходных данных
создаются исключительно конструктором и деструктором XmlElement.

.. code-block:: xml

    <classes>
        <class0>
            <grade>
                72
            </grade>
            <grade>
                97
            </grade>
            <grade>
                90
            </grade>
        </class0>
        <class1>
            <grade>
                77
            </grade>
            <grade>
                87
            </grade>
            <grade>
                56
            </grade>
        </class1>
    </classes>

Элемент <classes> создается переменной classes. Поскольку эта
переменная конструируется первой в main(), вывод содержит результат ее
конструирования первой. Поскольку эта переменная также уничтожается последней при выходе из функции
main(), вывод содержит результат вызова деструктора для ее уничтожения
last.

Конструктор копирования
-----------------------

Копирование - это создание нового объекта как копии существующего.
Если предположить, что S - это тип struct, то объекты копируются в следующих случаях:
* Передача объекта в функцию, которая принимает его по значению:

.. code-block:: d

    void foo(S s)
    { // The caller's argument is copied as the parameter
    // ...
    }

* Возвращение объекта из функции по значению:

.. code-block:: d

    S foo()
    {
        S result;
        // ...
        return result; // The return value is copied to the caller's context
    }

*Примечание: На практике эта копия исключается, если компилятор применяет "оптимизацию именованных возвращаемых значений
оптимизацию" (NRVO).*

* Копирование объектов в явном виде

В этом случае может возникнуть путаница, поскольку оператор присваивания
используется для копирования. Например, вторая строка в следующем коде является копированием то есть
построение вновь созданного объекта a из existingObject. Ключевое слово auto
указывает на то, что определяется (и конструируется) новый объект
конструируется)

.. code-block:: d

    auto existingObject = S();
    auto a = existingObject; // copy construction
    a = existingObject; // assignment
    a = a; // assignment
    a = S(); // assignment

Напротив, все строки, следующие за строкой построения копии, являются
присваиваниями, поскольку в этих строках a уже существует как объект.
По умолчанию копирование автоматически обрабатывается компилятором путем копирования
соответствующих членов объектов друг за другом. Предположим
следующее определение структуры и переменную a, которая копируется из
existingObject

.. code-block:: d

    struct S
    {
        int i;
        double d;
    }
    // ...
    auto existingObject = S();
    auto a = existingObject; // copy construction

Конструктор автоматического копирования выполняет следующие действия:
#. Копирование a.i из existingObject.i
#. Скопировать a.d из existingObject.d
Примером, когда автоматическое поведение не подходит, является тип Student
определенный в главе Structs (стр. 246), с которым возникла проблема при копировании
объектов этого типа:

.. code-block:: d

    struct Student
    {
        int number;
        int[] grades;
    }

Будучи slice, член grades этой структуры является ссылочным типом. Следствием
Следствием копирования объекта Student является то, что члены grades как в оригинале, так и в копии предоставляют доступ к одним и тем же фактическим элементам массива
оригинала и копии предоставляют доступ к одним и тем же фактическим элементам массива типа
int. В результате эффект изменения оценки через один из этих объектов
проявляется и в другом объекте:

.. code-block:: d

    auto student1 = Student(1, [ 70, 90, 85 ]);
    auto student2 = student1; // copy construction
    student2.number = 2;
    student1.grades[0] += 5; // this changes the grade of the
    // second student as well:
    assert(student2.grades[0] == 75);

Чтобы избежать такой путаницы, элементы оценок-членов второго
должны быть отдельными и принадлежать только этому объекту. Такое специальное поведение копирования
реализовано в конструкторе копирования.
Будучи конструктором, конструктор копирования также имеет такое имя и
не имеет возвращаемого типа. Тип его параметров должен быть того же типа, что и тип
struct и должен быть определен как ref. Поскольку исходный объект копии не должен быть
модифицироваться, уместно обозначить параметр как const (или inout).
В дополнение к ключевому слову this удобно назвать параметр как
that, чтобы обозначить "этот объект копируется из этого объекта":

.. code-block:: d

    struct Student
    {
        int number;
        int[] grades;
        this(ref const(Student) that)
        {
            this.number = that.number;
            this.grades = that.grades.dup;
        }
    }

Этот конструктор копирования копирует члены по одному, особенно следя за тем, чтобы
элементы классов копируются с помощью .dup. В результате новый объект получает свою собственную
копию элементов массива.
*Примечание: Как описано в разделе "Первое присваивание члену является конструкцией".
выше, эти операции присваивания на самом деле являются конструкциями копирования членов.*

Внесение изменений через первый объект не влияет на второй объект
больше:

.. code-block:: d

    student1.grades[0] += 5;
    assert(student2.grades[0] == 70)

Хотя это может сделать код менее читабельным, вместо того чтобы повторять тип
структуры, например, как Student, как в приведенном выше коде, тип параметра может быть обобщенно
как typeof(this) для всех структур:

.. code-block:: d

    this(ref const(typeof(this)) that)
    {
    // ...
    }

Postblit
--------
Это унаследованная функция D, которая не приветствуется. Вновь написанный код должен
использовать конструкторы копирования. Postblit все еще принимается для обратной совместимости,но несовместим с конструктором копирования: Если postblit
определен для типа, конструктор копирования отключается.
Унаследованный способ копирования объектов в D включает два шага:

#. Копирование членов существующего объекта в новый объект бит за битом. Этот
   называется blit, сокращение от block transfer.
#. Внесение дальнейших изменений в новый объект. Этот шаг называется postblit.
   Постблит называется так же, и у него нет возвращаемого типа. Чтобы
   отделить его от других конструкторов, его список параметров содержит ключевое слово
   this:

.. code-block:: d

    this(this)
    {
        // ...
    }

Основное отличие от конструктора копирования заключается в том, что члены
существующего объекта уже скопированы (blitted) в члены нового объекта к тому моменту, когда
когда постблит начинает выполняться. Кроме того, нет никакого объекта, о котором можно было бы говорить потому что постблит выполняется на новом объекте, используя только его члены.По
по этой причине все, что нужно (и возможно), - это внести изменения в новый
объект.
Функция postblit для структуры Student может быть записана следующим образом:

.. code-block:: d

    struct Student
    {
        int number;
        int[] grades;
        this(this)
        {
            // 'number' and 'grades' are already copied at this
            // point. We just need to make copies of the elements:
            grades = grades.dup;
        }
    }

Оператор присваивания
----------------------

Присвоение - это присвоение нового значения существующему объекту

code-block:: d

    returnTripDuration = tripDuration; // assignment

Присваивание  является более сложной операцией по сравнению с другими специальными операциями, потому что это
фактически является комбинацией двух операций:
* Уничтожение объекта левой стороны
* Копирование объекта правой стороны в объект левой стороны
Однако применять эти два действия в таком порядке рискованно, потому что оригинальный
объект будет уничтожен до того, как будет известно, что копирование будет успешным. Иначе,
исключение, возникающее во время операции копирования, может оставить левосторонний
объект в противоречивом состоянии: полностью уничтоженным, но не полностью скопированным.
По этой причине генерируемый компилятором оператор присваивания действует безопасно, применяя следующие шаги
применяя следующие шаги:

#. Скопировать объект правой стороны во временный объект.
   Это собственно копирование - половина операции присваивания. Поскольку в объекте левой стороны еще нет
   изменений в объекте левой стороны, он останется неповрежденным, если во время операции копирования возникнет исключение
   выброшенное во время этой операции копирования.
#. Уничтожить объект левой стороны Это вторая половина операции присваивания.
#. Передать временный объект объекту левой стороны.


Ни postblit, ни деструктор не выполняются ни во время, ни после этого шага. В результате,
объект левой стороны становится эквивалентом временного объекта.
После выполнения описанных выше шагов временный объект исчезает, и остается только объект правой стороны
боковой объект и его копия (т. е. левосторонний объект).
Хотя оператор присваивания, генерируемый компилятором, подходит в большинстве случаев,
он может быть определен программистом. При этом следует учитывать возможные
исключения и написать оператор присваивания таким образом, чтобы он работал даже при
наличии выброшенных исключений.
Синтаксис оператора присваивания следующий:

* Имя функции - opAssign
  Тип параметра совпадает с типом структуры. (Как и в конструкторе копирования
  конструктора, он также может быть ref const(typeof(this)).) Этот параметр
  часто называют rhs, сокращение от right-hand side. (Как и в конструкторе копирования, это
  также является подходящим именем).
* Тип возврата совпадает с типом структуры.
* Выход из функции осуществляется с помощью return this.


В качестве примера рассмотрим простую структуру Duration, в которой оператор присваивания
выводит сообщение:

.. code-block:: d

    struct Duration
    {
        int minute;
        Duration opAssign(Duration rhs)
        {
            writefln("minute is being changed from %s to %s",
                this.minute, rhs.minute);
            this.minute = rhs.minute;
            return this;
        }
    }

    // ...
    auto duration = Duration(100);
    duration = Duration(200); // assignment

Вывод

.. code-block:: d

    minute is being changed from 100 to 200

**Присваивание из других типов**

Иногда бывает удобно присваивать значения типов, отличных от
типа структуры. Например, вместо того чтобы требовать объект Duration в
в правой части, может быть полезно присвоить значение из целого числа:

.. code-block:: d

    duration = 300;

Это можно сделать, определив другой оператор присваивания, который принимает параметр int
параметр:

.. code-block:: d

    struct Duration
    {
        int minute;
        Duration opAssign(Duration rhs)
        {
            writefln("minute is being changed from %s to %s", this.minute, rhs.minute);
            this.minute = rhs.minute;
            return this;
        }

        Duration opAssign(int minute)
        {
            writefln("minute is being replaced by an int");
            this.minute = minute;
            return this;
        }
    }
    // ...
    duration = Duration(200);
    duration = 300;

Вывод

.. code-block:: console

    minute is being changed from 100 to 200
    minute is being replaced by an int

*Примечание: Несмотря на удобство, назначение разных типов друг другу может вызвать
путаницы или ошибок.*

Отключение функций-членов
-------------------------

Функции, объявленные как @disable, не могут быть использованы.
Если для членов типа не существует разумных значений по умолчанию, его конструктор по умолчанию
конструктор по умолчанию может быть отключен. Например, может быть некорректно для следующего
типа иметь пустое имя файла

.. code-block:: d

    struct Archive
    {
        string fileName;
    }

К сожалению, сгенерированный компилятором конструктор по умолчанию инициализирует
fileName как пустой:

.. code-block:: d

    auto archive = Archive(); // ← fileName member is empty

Конструктор по умолчанию можно явно отключить, объявив его как @disable, чтобы
чтобы объекты создавались с помощью одного из других конструкторов. При этом нет
необходимости предоставлять тело для отключенной функции:

.. code-block:: d

    struct Archive
    {
        string fileName;
        @disable this(); // ← cannot be called
        this(string fileName)
        { // ← can be called
            // ...
        }
    }
    // ...
    auto archive = Archive(); // ← ошибка компиляции

На этот раз компилятор не позволяет вызвать this():

.. code-block:: console

    Error: constructor deneme.Archive.this is not callable because it is annotated with @disable

Объекты Archive должны быть созданы либо с помощью одного из других конструкторов
либо явно с помощью значения .init:

.. code-block:: d

    auto a = Archive("records"); // ← compiles
    auto b = Archive.init; // ← compiles

Коструктор копирования, функция postblit и оператор присваивания также могут быть отключены.

.. code-block:: d

    struct Archive
    {
        // ...
        // Disables the copy constructor
        @disable this(ref const(typeof(this)));
        // Disables the postblit
        @disable this(this);
        // Disables the assignment operator
        @disable typeof(this) opAssign(ref const(typeof(this)));
    }
    // ...
    auto a = Archive("records");
    auto b = a; // ← compilation ERROR
    b = a;

Отключение конструктора копирования и постблита может помочь в тех случаях, когда
деструкторы выполняют операции, которые должны быть выполнены только один раз. Копирование
объектов таких типов может привести к ошибкам, поскольку деструктор будет выполняться для
нескольких копий.
Например, следующий деструктор намерен записать финальное сообщение "Завершение работы"
в файл, который используется для протоколирования:

.. code-block:: d

    import std.stdio;
    import std.datetime;

    struct Logger
    {
        File file;
        this(File file)
        {
            this.file = file;
            log("Started");
        }

        ~this()
        {
            log("Finishing"); // ← Intended to be the last message
        }

        void log(string message)
        {
            file.writefln("%s %s", Clock.currTime(), message);
        }
    }

    void main()
    {
        auto logger = Logger(stdout);
        logger.log("Working inside main");
        logger.log("Calling foo");
        foo(logger);
        logger.log("Back to main");
    }

    void foo(Logger logger)
    {
        logger.log("Working inside foo");
    }

Вывод программы показывает, что программа не работает так, как задумано
потому что финальное сообщение появляется более одного раза:

.. code-block:: console

    2022-Jan-03 22:21:24.3143894 Started
    2022-Jan-03 22:21:24.3144467 Working inside main
    2022-Jan-03 22:21:24.3144628 Calling foo
    2022-Jan-03 22:21:24.3144767 Working inside foo
    2022-Jan-03 22:21:24.3144906 Finishing
    2022-Jan-03 22:21:24.3145035 Back to main
    2022-Jan-03 22:21:24.3145155 Finishing

Проблема возникает из-за того, что создается более одного объекта Logger и
деструктор выполняется для каждого из них. Объектом, вызывающим непреднамеренное
раннее сообщение "Завершение работы", является параметр foo, который копируется, потому что он является побочным
значение.
Самое простое решение в таких случаях - отключить копирование и присваивание
вообще:

.. code-block:: d

    struct Logger
    {
        @disable this(this);
        @disable this(ref const(typeof(this)));
        @disable Logger opAssign(ref const(typeof(this)));
        // ...
    }

Поскольку Logger больше нельзя копировать, необходимо изменить foo так, чтобы он принимал свой параметр
по ссылке

.. code-block:: d

    void foo(ref Logger logger)
    {
    // ...
    }

Резюме
-------

* Конструктор (this) предназначен для подготовки объектов к использованию. В большинстве случаев достаточно сгенерированного компилятором
  конструктор по умолчанию в большинстве случаев достаточен.
* Поведение конструктора по умолчанию не может быть изменено в структурах; вместо него можно использовать статический
  opCall может быть использован вместо него.
* Однопараметрические конструкторы можно использовать при преобразовании типов с помощью to и
  приведения.
* Деструктор (~this) предназначен для операций, которые должны быть выполнены, когда
  когда заканчивается время жизни объектов.
* Конструктор копирования (this(ref const(typeof(this)))))) предназначен для определения того, как
  объект копируется из существующего.
* Постблит (this(this)) не рекомендуется использовать в новом коде; он предназначен для корректировки
  объекта после автоматического копирования его членов.
* Оператор присваивания (opAssign) предназначен для изменения значений существующих объектов.
* Функции-члены могут быть отключены с помощью @disable.
